#include <iostream>
#include <memory>

namespace {

struct Foo
{
    Foo() { std::cout << "Foo::Foo\n"; }
    ~Foo() { std::cout << "Foo::~Foo\n"; }
    void bar() { std::cout << "Foo::bar data =" << data++ << std::endl; }
    int data = 0;
};

void f(const Foo &)
{
    std::cout << "f(const Foo&)\n";
}

struct D
{
    void operator()(Foo* foo)
    {
        std::cout << "D operator()" << std::endl;
        delete foo;
    }
};


void TestAutoDestroy()
{
    //1. 普通的new对象.
    std::cout << "TestDestroy...................." << std::endl;
    {
        std::unique_ptr<Foo> p1(new Foo);
    }
    //2. 普通的new[]对象.
    {
        std::unique_ptr<Foo[]> p2(new Foo[4]);
    }
    //3. 自定义的deleter.
    {
        std::unique_ptr<Foo, D> p3(new Foo);
    }
}

void TestOwner()
{
    std::cout << "TestOwner...................." << std::endl;
    //1. new object.
    std::unique_ptr<Foo> p1(new Foo);  // p1 owns Foo
    if (p1) p1->bar();

    {
        std::unique_ptr<Foo> p2(std::move(p1));  // now p2 owns Foo
        f(*p2);

        p1 = std::move(p2);  // ownership returns to p1
        std::cout << "after move p2 = " << p2.get() << std::endl;
        // 因为类里定义的成员函数，不算在类对象的内存空间里，它是专门存放在一个函数表里的
        // 所以move之后，原来的unique_ptr不能再访问类对象的内存空间，但是可以继续访问类的函数表，不过也有限制
        // 函数里不能access原先类对象的空间，如果access到，就会发生段错误
        // p2->bar();
        if (p2)
            p2->bar();
        std::cout << "destroying p2...\n";
    }

    Foo *p = p1.release(); // 释放指针占用，不释放内存
    // p1.reset(); // 重置新的占用指针，释放旧内存空间
    // p->bar();
    p1->bar();
}

void TestArrayOwner()
{
    std::cout << "TestArrayOwner...................." << std::endl;
    //1. new[] object.
    std::unique_ptr<Foo[]> p1(new Foo[4]);  // p1 owns Foo
    if (p1) p1[0].bar();

    {
        std::unique_ptr<Foo[]> p2(std::move(p1));  // now p2 owns Foo
        f(p2[0]);

        p1 = std::move(p2);  // ownership returns to p1
        p2[0].bar();
        std::cout << "destroying p2...\n";
    }

    p1[0].bar();
}


void Handle() {
    TestAutoDestroy();
    TestOwner();
    TestArrayOwner();
}

}

void HandleUniquePtr() {
    Handle();
}
